/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef __MESOS_EXECUTOR_HPP__
#define __MESOS_EXECUTOR_HPP__

#include <pthread.h>

#include <string>

#include <mesos/mesos.hpp>

/**
 * Mesos executor interface and executor driver. An executor is
 * responsible for launching tasks in a framework specific way (i.e.,
 * creating new threads, new processes, etc). One or more executors
 * from the same framework may run concurrently on the same
 * machine. Note that we use the term "executor" fairly loosely to
 * refer to the code that implements the Executor interface (see
 * below) as well as the program that is responsible for instantiating
 * a new MesosExecutorDriver (also below). In fact, while a Mesos
 * slave is responsible for (forking and) executing the "executor",
 * there is no reason why whatever the slave executed might itself
 * actually execute another program which actually instantiates and
 * runs the MesosSchedulerDriver. The only contract with the slave is
 * that the program that it invokes does not exit until the "executor"
 * has completed. Thus, what the slave executes may be nothing more
 * than a script which actually executes (or forks and waits) the
 * "real" executor.
 *
 * IF YOU FIND YOURSELF MODIFYING COMMENTS HERE PLEASE CONSIDER MAKING
 * THE SAME MODIFICATIONS FOR OTHER LANGUAGE BINDINGS (e.g., Java:
 * src/java/src/org/apache/mesos, Python: src/python/src, etc.).
 */

namespace mesos {

// A few forward declarations.
class ExecutorDriver;

namespace internal {
class ExecutorProcess;
}

/**
 * Callback interface to be implemented by frameworks' executors. Note
 * that only one callback will be invoked at a time, so it is not
 * recommended that you block within a callback because it may cause a
 * deadlock.
 *
 * Each callback includes a pointer to the executor driver that was
 * used to run this executor. The pointer will not change for the
 * duration of an executor (i.e., from the point you do
 * ExecutorDriver::start() to the point that ExecutorDriver::join()
 * returns). This is intended for convenience so that an executor
 * doesn't need to store a pointer to the driver itself.
 */
// TODO(bmahler): Consider adding a usage() callback here, that provides
// information to the executor about it's ResourceUsage.
class Executor
{
public:
  /**
   * Empty virtual destructor (necessary to instantiate subclasses).
   */
  virtual ~Executor() {}

  /**
   * Invoked once the executor driver has been able to successfully
   * connect with Mesos. In particular, a scheduler can pass some
   * data to its executors through the FrameworkInfo.ExecutorInfo's
   * data field.
   */
  virtual void registered(ExecutorDriver* driver,
                          const ExecutorInfo& executorInfo,
                          const FrameworkInfo& frameworkInfo,
                          const SlaveInfo& slaveInfo) = 0;

  /**
   * Invoked when the executor re-registers with a restarted slave.
   */
  virtual void reregistered(ExecutorDriver* driver,
                            const SlaveInfo& slaveInfo) = 0;

  /**
   * Invoked when the executor becomes "disconnected" from the slave
   * (e.g., the slave is being restarted due to an upgrade).
   */
  virtual void disconnected(ExecutorDriver* driver) = 0;

  /**
   * Invoked when a task has been launched on this executor (initiated
   * via Scheduler::launchTasks). Note that this task can be realized
   * with a thread, a process, or some simple computation, however, no
   * other callbacks will be invoked on this executor until this
   * callback has returned.
   */
  virtual void launchTask(ExecutorDriver* driver,
                          const TaskInfo& task) = 0;

  /**
   * Invoked when a task running within this executor has been killed
   * (via SchedulerDriver::killTask). Note that no status update will
   * be sent on behalf of the executor, the executor is responsible
   * for creating a new TaskStatus (i.e., with TASK_KILLED) and
   * invoking ExecutorDriver::sendStatusUpdate.
   */
  virtual void killTask(ExecutorDriver* driver, const TaskID& taskId) = 0;

  /**
   * Invoked when a framework message has arrived for this
   * executor. These messages are best effort; do not expect a
   * framework message to be retransmitted in any reliable fashion.
   */
  virtual void frameworkMessage(ExecutorDriver* driver,
                                  const std::string& data) = 0;

  /**
   * Invoked when the executor should terminate all of its currently
   * running tasks. Note that after a Mesos has determined that an
   * executor has terminated any tasks that the executor did not send
   * terminal status updates for (e.g., TASK_KILLED, TASK_FINISHED,
   * TASK_FAILED, etc) a TASK_LOST status update will be created.
   */
  virtual void shutdown(ExecutorDriver* driver) = 0;

  /**
   * Invoked when a fatal error has occured with the executor and/or
   * executor driver. The driver will be aborted BEFORE invoking this
   * callback.
   */
  virtual void error(ExecutorDriver* driver, const std::string& message) = 0;
};


/**
 * Abstract interface for connecting an executor to Mesos. This
 * interface is used both to manage the executor's lifecycle (start
 * it, stop it, or wait for it to finish) and to interact with Mesos
 * (e.g., send status updates, send framework messages, etc.). See
 * MesosExecutorDriver below for a concrete example of an
 * ExecutorDriver.
 */
class ExecutorDriver
{
public:
  /**
   * Empty virtual destructor (necessary to instantiate subclasses).
   */
  virtual ~ExecutorDriver() {}

  /**
   * Starts the executor driver. This needs to be called before any
   * other driver calls are made.
   */
  virtual Status start() = 0;

  /**
   * Stops the executor driver.
   */
  virtual Status stop() = 0;

  /**
   * Aborts the driver so that no more callbacks can be made to the
   * executor. The semantics of abort and stop have deliberately been
   * separated so that code can detect an aborted driver (i.e., via
   * the return status of ExecutorDriver::join, see below), and
   * instantiate and start another driver if desired (from within the
   * same process ... although this functionality is currently not
   * supported for executors).
   */
  virtual Status abort() = 0;

  /**
   * Waits for the driver to be stopped or aborted, possibly
   * _blocking_ the current thread indefinitely. The return status of
   * this function can be used to determine if the driver was aborted
   * (see mesos.proto for a description of Status).
   */
  virtual Status join() = 0;

  /**
   * Starts and immediately joins (i.e., blocks on) the driver.
   */
  virtual Status run() = 0;

  /**
   * Sends a status update to the framework scheduler, retrying as
   * necessary until an acknowledgement has been received or the
   * executor is terminated (in which case, a TASK_LOST status update
   * will be sent). See Scheduler::statusUpdate for more information
   * about status update acknowledgements.
   */
  virtual Status sendStatusUpdate(const TaskStatus& status) = 0;

  /**
   * Sends a message to the framework scheduler. These messages are
   * best effort; do not expect a framework message to be
   * retransmitted in any reliable fashion.
   */
  virtual Status sendFrameworkMessage(const std::string& data) = 0;
};


/**
 * Concrete implementation of an ExecutorDriver that connects an
 * Executor with a Mesos slave. The MesosExecutorDriver is thread-safe.
 *
 * The driver is responsible for invoking the Executor callbacks as it
 * communicates with the Mesos slave.
 *
 * Note that blocking on the MesosExecutorDriver (e.g., via
 * MesosExecutorDriver::join) doesn't affect the executor callbacks in
 * anyway because they are handled by a different thread.
 *
 * Note that the driver uses GLOG to do its own logging. GLOG flags can
 * be set via environment variables, prefixing the flag name with
 * "GLOG_", e.g., "GLOG_v=1". For Mesos specific logging flags see
 * src/logging/flags.hpp. Mesos flags can also be set via environment
 * variables, prefixing the flag name with "MESOS_", e.g.,
 * "MESOS_QUIET=1".
 *
 * See src/examples/test_executor.cpp for an example of using the
 * MesosExecutorDriver.
 */
class MesosExecutorDriver : public ExecutorDriver
{
public:
  /**
   * Creates a new driver that uses the specified Executor. Note, the
   * executor pointer must outlive the driver.
   */
  explicit MesosExecutorDriver(Executor* executor);

  /**
   * This destructor will block indefinitely if
   * MesosExecutorDriver::start was invoked successfully (possibly via
   * MesosExecutorDriver::run) and MesosExecutorDriver::stop has not
   * been invoked.
   */
  virtual ~MesosExecutorDriver();

  /**
   * See ExecutorDriver for descriptions of these.
   */
  virtual Status start();
  virtual Status stop();
  virtual Status abort();
  virtual Status join();
  virtual Status run();
  virtual Status sendStatusUpdate(const TaskStatus& status);
  virtual Status sendFrameworkMessage(const std::string& data);

private:
  friend class internal::ExecutorProcess;

  Executor* executor;

  // Libprocess process for communicating with slave.
  internal::ExecutorProcess* process;

  // Mutex to enforce all non-callbacks are execute serially.
  pthread_mutex_t mutex;

  // Condition variable for waiting until driver terminates.
  pthread_cond_t cond;

  // Current status of the driver.
  Status status;
};

} // namespace mesos {

#endif // __MESOS_EXECUTOR_HPP__
